/*
<license>
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
</license>
*/
#ifndef GENERIK_STRINGUTILS_H
#define GENERIK_STRINGUTILS_H

/** \file stringutils.h
This file contains a couple of string utilities.
They are mostly convenience utilities to make code less cluttered.

\todo This code needs a lot of refactoring and general work.
*/

#include <list>
#include <vector>
#include <map>
#include <ostream>
#include <sstream>
#include <algorithm>
#include <stdlib.h>

#include "tracing.h"

namespace generik
{

	/// converts an input value into an output type
	template <typename TO, typename FROM>
	inline TO to_(const FROM& value)
	{
		REM_GENERIK_TRACER;
		std::stringstream ss;
		TO value_out;
		ss<<value;
		ss>>value_out;
		return value_out;
	}

	/// converts an value into an output string
	template <typename FROM>
	inline std::string to_string(const FROM& value)
	{
		REM_GENERIK_TRACER;
		std::stringstream ss;
		ss<<value;
		return ss.str();
	}
	
	template <typename TYPE>
	inline std::string eat_string(TYPE*& value, bool destroy = true)
	{
		if (value)
		{
			std::string result = generik::to_string(*value);
			if (destroy)
			{
				free(value);
				value = 0;
			}
			return result;
		}
		return "";
	}
	
	/** Eats a character array and spits out the corresponding string.
	If the input pointer is null, an empty string is returned.
	If destroy is true (the default) the character array is automatically freed!
	*/
	template <>
	inline std::string eat_string(char*& value, bool destroy)
	{
		if (value)
		{
			std::string result = value;
			if (destroy)
			{
				free(value);
				value = 0;
			}
			return result;
		}
		return "";
	}

	/// returns the lower case version of the input string
	struct to_lower_func
	{
		char operator()(char c) const { return tolower(c); }
	};
	inline std::string to_lower(const std::string& mixed_in)
	{
		REM_GENERIK_TRACER;
		std::string lower = mixed_in;
		std::transform(mixed_in.begin(), mixed_in.end(), lower.begin(), to_lower_func());
		return lower;
	}
	/// returns the upper case version of the input string
	struct to_upper_func
	{
		char operator()(char c) const { return toupper(c); }
	};
	inline std::string to_upper(const std::string& mixed_in)
	{
		REM_GENERIK_TRACER;
		std::string upper = mixed_in;
		std::transform(upper.begin(), upper.end(), upper.begin(), to_upper_func());
		return upper;
	}
	
	/// returns the first word of the input sentence
	inline std::string first_word(const std::string& sentence_in)
	{
		REM_GENERIK_TRACER;
		std::string first = "";
		std::size_t a = sentence_in.find_first_not_of(" \t\n");
		if (a != std::string::npos)
		{
			std::size_t b = sentence_in.find_first_of(" \t\n", a);
			if (b != std::string::npos)
			{
				first = sentence_in.substr(a, b-a);
			}
			else
			{
				first = sentence_in.substr(a);
			}
		}
		GENERIK_TRACE_VALUE(first);
		return first;
	}
	/// returns whatever occurs after the first word in the sentence
	inline const std::string rest_of(const std::string& sentence_in)
	{
		REM_GENERIK_TRACER;
		std::size_t i = sentence_in.find_first_of(" \t\n");
		i = sentence_in.find_first_not_of(" \t\n", i);
		std::string rest = "";
		if (i != std::string::npos)
		{
			rest = sentence_in.substr(i);
		}
		GENERIK_TRACE_VALUE(rest);
		return rest;
	}

	typedef std::pair<std::string, std::string> splitstring;

	inline std::ostream& operator<<(std::ostream& stream, const splitstring& s)
	{
	   return stream<<s.first<<"|"<<s.second;
	}

	inline splitstring split(const std::string& sentence_in)
	{
		REM_GENERIK_TRACER;
		//
		const char* whitespace = " \t\n";
		const char quote 	   	= '"';
		const char singlequote 	= '\'';
		const char backslash  	= '\\';
		//
		char currentquote;
		//
		splitstring result;
		// find the first occurence which is not whitespace
		// if it is a quotation mark, then find the next quotation mark which is
		// not preceded by a backslash
		std::string::size_type a = sentence_in.find_first_not_of(whitespace);
		if (a == std::string::npos)
		{
			GENERIK_TRACE("nothing but whitespace found - exiting");
			return result; // check for none found
		}
		if (sentence_in[a] == quote || sentence_in[a] == singlequote)
		{
			GENERIK_TRACE("quote found");
			currentquote = sentence_in[a];
			// quote found, look for closing quote:
			std::string::size_type b = a;
			while (true)
			{
				b = sentence_in.find_first_of(currentquote, b+1);
				if (b == std::string::npos)
				{
					GENERIK_TRACE("quote not closed");
					//GENERIK_TRACE("adding closing quote");
					result.first = sentence_in.substr(a+1); // + "\"";
					return result; // check for none found
				}
				if (sentence_in[b-1] == backslash)
				{
					GENERIK_TRACE("backslashed quote found, continuing...");
					// backslash found, look again
				}
				else
				{
					GENERIK_TRACE("valid quote found");
					// found the end, return it - with quotation marks..
					result.first  = sentence_in.substr(a+1, b-a-1);
					if (b+1 < sentence_in.size())
					{
						result.second = sentence_in.substr(b+1); //, std::string::npos);
					}
					break;
				}
			}
		}
		else
		{
			GENERIK_TRACE("no quote found");
			// if it is not, find the first occurence of whitespace
			std::size_t b = sentence_in.find_first_of(whitespace, a);
			if (b == std::string::npos)
			{
				GENERIK_TRACE("one word sentence");
				result.first = sentence_in.substr(a, b);
			}
			else
			{
				GENERIK_TRACE("easy split");
				result.first  = sentence_in.substr(a, b-a);
				result.second = sentence_in.substr(b); //, std::string::npos);
			}
		}
		GENERIK_TRACE_VALUE(result);
		return result;
	}

	typedef std::list<std::string> stringlist;
	typedef std::vector<std::string> arglist;
	
	template <typename LIST>
	inline LIST slice(const std::string& sentence_in)
	{
		LIST container;
		splitstring pair = split(sentence_in);
		while (!pair.first.empty())
		{
			container.push_back(pair.first);
			pair = split(pair.second);
		}
		return container;
	}
	
	/*
	inline std::list<std::string> slice(const std::string& sentence_in)
	{
		std::list<std::string> list;
		splitstring pair = split(sentence_in);
		while (!pair.first.empty())
		{
			list.push_back(pair.first);
			pair = split(pair.second);
		}
		return list;
	}
	inline std::vector<std::string> slice_args(const std::string& sentence_in)
	{
		std::vector<std::string> list;
		splitstring pair = split(sentence_in);
		while (!pair.first.empty())
		{
			list.push_back(pair.first);
			pair = split(pair.second);
		}
		return list;
	}
	*/
	


	/// returns true if the child is in the parent, and has sub-children of its own
	/** this can be used to identify non-empty directories, when child is a set
	of files
	*//*
	bool is_child(const std::string& parent, const std::string& child)
	{
		// if the child is shorter than the parent it is automatically false
		if (child.size() < parent.size()) return false;
		if (child.substr(0, parent.size()) == parent) return true;
		return false;
	}
	*/

	/// strips quotes (if any) from a string
	inline std::string stripquotes(const std::string& sentence_in)
	{
		std::string sentence_out;
		std::size_t a = sentence_in.find_first_not_of(" \t\n");
		if (a == std::string::npos) return "";
		if (sentence_in[a] == '"')
		{
			// found a starting quote - look for the ending quote, and
			// return the contents
			std::size_t b = sentence_in.find_last_of("\"");
			if (b != std::string::npos)
			{
				sentence_out = sentence_in.substr(a+1, b-a-1);
			}
			else
			{
				sentence_out = sentence_in.substr(a+1);
			}
		}
		else
		{
			// unquoted string, use the entire thing
			sentence_out = sentence_in;
		}
		return sentence_out;
	}
	
	/// duplicates any single quotes in a string
	inline std::string dupquotes(const std::string& sentence_in)
	{
		// strategy:
		/*
		start at 0, look for first occurence of \' -> into a
		while a != string::npos
			insert \' at a
			start at a+2, look for first occurence of \' -> into a
		*/
		std::string sentence_out = sentence_in;
		std::size_t a = sentence_out.find_first_of("'", 0);
		while (a != std::string::npos)
		{
			sentence_out.insert(a, "'");
			a = sentence_out.find_first_of("'", a+2);
		}
		return sentence_out;
	}

	/// returns true if the name is a uri
	/** Currently checks for "://" at the start of the name.
	If found, it returns true.
	\note This might be a bit of a fudge...
	*/
	inline bool isa_uri(const std::string& name_in)
	{
		GENERIK_TRACER;
		std::size_t a  = name_in.find_first_of(":/");
		if (a != std::string::npos)
		{
			if (name_in.substr(a, 3) == "://")
			{
				GENERIK_TRACE("Found a uri prefix: ["<<name_in.substr(0, a)<<"]");
				return true;
			}
		}
		return false;
	}

	/// returns true if the path starts with a slash
	/** This should indicate that the given path is an absolute path.
	\note This is *definitely* a fudge
	*/
	inline bool isa_abspath(const std::string& path_in)
	{
		std::size_t a = path_in.find_first_not_of(" \t\n");
		if (a == std::string::npos) return false;
		if (path_in[a] == '/')
		{
			return true;
		}
		return false;
	}

	/// wraps the argument in double quotes
	inline std::string quote(const std::string& sentence_in)
	{
		std::string quoted;
		quoted = "\""; quoted += sentence_in + "\"";
		return quoted;
	}

	/*
	std::string escape0(const std::string& sentence_in)
	{
		std::string escaped;
		uint i = 0;
		while(true)
		{
			if (i == sentence_in.length()) break;
			switch (sentence_in[i])
			{
			case '"':
			case '/':
			case ' ':
			case '\t':
				{
					escaped += "\\";
					break;
				}
			case '\\':
				{
					escaped += "\\";
					if (++i == sentence_in.length()) break;
					escaped += sentence_in[i++];
					continue;
				}
			default:
				// noop
				{}
			}
			escaped += sentence_in[i++];
		}
		return escaped;
	}
	*/

	/*
	std::string escape(const std::string& sentence_in)
	{
		std::string escaped;
		if (sentence_in.find_first_of(" \t/\"\\") != std::string::npos)
		{
			escaped = "\"";
			escaped += sentence_in + "\"";
		}
		else
		{
			escaped = sentence_in;
		}
		return escaped;
	}
	*/

	/// returns the argument with any single quotes as escaped single quotes
	inline std::string escape_singlequotes(const std::string& sentence_in)
	{
		std::string escaped;
		unsigned int i = 0;
		while(true)
		{
			if (i == sentence_in.length()) break;
			switch (sentence_in[i])
			{
			case '\'':
				{
					escaped += "\'";
					break;
				}
			default:
				{
				}
			}
			escaped += sentence_in[i++];
		}
		return escaped;
	}

	//#include "boost/filesystem/operations.hpp"
	//#include "boost/filesystem/path.hpp"

	//#include <sys/types.h>
	//#include <sys/stat.h>
	//#include <errno.h>

	/*
	bool isa_directory(const std::string& file_in)
	{
		GENERIK_TRACER_SILENT;
		std::string file = escape(file_in);
		GENERIK_TRACE_VALUE(file);
		//boost::filesystem::path path(file);
		//if (!boost::filesystem::exists(path)) return false;
		//return boost::filesystem::is_directory(path);
		struct stat statinfo;
		if (!stat(file_in.c_str(), &statinfo))
		{
			GENERIK_TRACE_VALUE(statinfo.st_mode);
			if (statinfo.st_mode & S_IFDIR) return true;
		}
		else
		{
			GENERIK_TRACE("error occurred: ["<<errno<<"] "<<strerror(errno));
		}
		return false;
	}

	bool isa_file(const std::string& file_in)
	{
		//boost::filesystem::path path(file_in);
		//if (!boost::filesystem::exists(path)) return false;
		//return !boost::filesystem::is_directory(path);
		return true;
	}
	*/

	/// returns the fullpath to path_in, which is taken as relative to base_in.
	/** it is a fudge, and relies on path_in to lie directly under base_in
	*/
	inline std::string fullpath(const std::string& base_in, const std::string& path_in)
	{
		std::string path;
		if (isa_abspath(path_in))
		{
			path = path_in;
		}
		else
		{
			path = base_in + "/" + path_in;
		}
		return path;
	}

	/// returns the relative path from base_in to path_in.
	/** it is a fudge at the moment - relies upon path_in lying directly under base_in
	*/
	inline std::string relativepath(const std::string& base_in, const std::string& path_in)
	{
		if (base_in.length() <= path_in.length())
		{
			return path_in.substr(base_in.length() + 1);
		}
		else
		{
			return "";
		}
	}

	//#include <generik/iterator.h>

	/// outputs numbered tracks
	/** outputs a track, numbering each sequential track accordingly
	This is useful, since MPD cannot seem to count.
	*/
	/*
	struct trackoutput_iterator
	: generik::iterator::output::counting<track>
	{
		typedef generik::iterator::output::counting<track> ITERATOR;

		trackoutput_iterator(std::ostream& stream_in, const char* s_in = "")
		: ITERATOR(stream_in, s_in)
		{}
		ITERATOR& operator=(const track& track_in)
		{
			_stream<<"file: "	<<(track_in.file)<<_s;
			_stream<<"Time: 0"	<<_s;
			_stream<<"Artist: "	<<(track_in.artist)<<_s;
			_stream<<"Title: "	<<(track_in.title)<<_s;
			_stream<<"Album: "	<<(track_in.album)<<_s;
			_stream<<"Track: "	<<(track_in.number)<<_s;
			_stream<<"Date: 0"	<<_s;
			_stream<<"Genre: "	<<(track_in.genre)<<_s;
			_stream<<"Pos: "	<<_count<<_s;
			_stream<<"Id: "		<<_count<<_s;
			return *this;
		}
	};
	*/

	/*
	DEPRECATED
	cuts a line into a list of strings.
	The line must be in the format
		'item' 'item' 'item' ...
	and the list will contain the (single-quote-removed) items.
	Single quotes inside the items must be escaped
	inline std::list<std::string> cut(const std::string& line_in)
	{
		std::cout<<"line: ["<<line_in<<"]"<<std::endl;
		std::list<std::string> items;
		std::string::size_type a = 0;
		std::string::size_type b = 0;
		//
		a = line_in.find_first_of("'");
		if (a != std::string::npos)
		{
			if (a > 0)
			{
				while (line_in[a-1] == '\\')
				{
					a = line_in.find_first_of("'", a + 1);
					if (a == std::string::npos)
					{
						return items;
					}
				}
			}
		}
		while (a != std::string::npos)
		{
			std::cout<<"a: "<<a<<std::endl;
			b = line_in.find_first_of("'", a+1);
			if (b != std::string::npos)
			{
				while (line_in[b-1] == '\\')
				{
					b = line_in.find_first_of("'", b + 1);
					if (b == std::string::npos)
					{
						items.push_back(line_in.substr(a + 1));
						return items;
					}
				}
			}
			std::cout<<"ab: "<<a<<" "<<b<<std::endl;
			items.push_back(line_in.substr(a + 1, b - a - 1));
			//
			a = line_in.find_first_of("'", b + 1);
			if (a != std::string::npos)
			{
				while (line_in[a-1] == '\\')
				{
					a = line_in.find_first_of("'", a + 1);
					if (a == std::string::npos)
					{
						return items;
					}
				}
			}
		}
		return items;
	}
	*/
	
	/** Copies a std::string's contents to a char array.
	This function is useful when interfacing C++ back to C. It takes a 
	std::string as input, allocates enough space using malloc and then 
	copies the data from the std::string to the allocated char array.
	
	\note The result memory should be freed using free() after use.
	Remember, you are back in C land...
	*/
	inline char* copy(const std::string& str)
	{
		int n = str.length()+16;
		char* buf = (char*)malloc(n);
		int length = str.copy(buf, n);
		buf[length] = '\0';
		return buf;
	}

	inline std::string strip(const std::string& value)
	{
		size_t a = value.find_first_not_of(" \t\n");
		size_t b = value.find_last_not_of(" \t\n");
		return value.substr(a, b-a+1);
	}
	inline std::string stripleft(const std::string& value)
	{
		size_t a = value.find_first_not_of(" \t\n");
		return value.substr(a);
	}
	inline std::string stripright(const std::string& value)
	{
		size_t b = value.find_last_not_of(" \t\n");
		return value.substr(0, b+1);
	}

}

#endif

